RAM unter dem ROM

← Zurück zum Forum

Hallo,

ich wollte mal den RAM unter dem OS-ROM (Speicherbereich $c000-$cfff und $e000-$ffff) auf dem 800XL/XE nutzen.

Da ja das OS den VBL initialisiert muss man den ausschalten, kann dann das RAM unter dem ROM einblenden und dann für eigene Zwecke nutzen. Das funktioniert auch bestens (s. Beispielcode unten)

Nur, wie lade ich den RAM-Bereich Programm oder Daten?

.org funktioniert in dem Bereich ja nicht, weil zum Zeitpunkt des Ladens ja das OS noch angeschaltet ist.

    run start                   

    org $0600   ; Page 6 fuer diesen Test genommen

start

wait 
    lda $14     ; rtclock-Warten, damit der Interrupt nicht gerade reinrauscht, beim Abschalten
    cmp $14
    beq wait

    ;VBI-Interrupt aus
    sei
    lda #0
    sta $d40e

    ; ROM aus
    lda #$fe
    sta $d301

    ; spasseshalber den Farbwert für Rot in die Speicheradresse $e000 schreiben

    lda #52
    sta $e000

    ; Dann wieder dort auslesen
    lda $e000
    ; und damit die Hintergrundfarbe setzen
    sta $2c8

    ; nun das ROM wieder einschalten
    lda #$ff
    sta $d301

    ; jetzt auch wieder den VBI-Interrupt wieder einschalten
    lda #$40
    sta $d40e
    cli


endloop jmp endloop 

So. Muss mich mal selbst zitieren, da ich es gerade hinbekommen habe. Man muss das Schlüsselwort INI benutzen, das eine Routine startet, bevor der Rest des Programmes geladen wird.

Hier ein kleines Beispielprogramm, das im Speicherbereich des ROMs Programmcode hinterlegt, der den Farbwert des Bildschirmrahmens ändert (zunächst rot, dann auf Joystickknopf weiss).

Das Progrämmchen schaltet in der INI-Routine auch gleich noch das BASIC weg, damit braucht der Anwender nicht mehr OPTION beim Anschalten des ATARIs gedrückt halten.

Der Quelltext (MADS Assembler) sollte selbsterklärend sein.

Nun ist die nächste Frage, was ich tun muss, damit DLI und VBI auch noch funktionieren, wenn das RAM unter dem ROM eingeschaltet ist?

color4      equ $02c8   ; Farbe für den Rahmen oder Hintergrundfarbe (je nach Bildschirmmodus)

nmien       equ $d40e   ; Steuerregister für die nicht maskierbaren Interrupts

portb       equ $d301   ; schaltet BASIC aus $a000-$bfff und RAM unter dem ROM ein $c000-$cfff + $e000-$ffff    

rtclok      equ $12

trig0       equ $d010   ; Joystickknopf Joystick0

    org $2000   

initialize
    lda portb
    ora $2          ; BASIC ausschalten
    sta portb

wait    lda rtclok+2        ; rtclock-Warten, damit der Interrupt nicht gerade reinrauscht, beim Abschalten
    cmp rtclok+2        ; denn rtclock wird bei jedem VBI um eins erhöht
    beq wait

    ;VBI-Interrupt aus
    sei
    lda #0
    sta nmien

    ; ROM aus
    lda #$fe
    sta portb
    rts

        ini initialize      ; Dies bewirkt, dass dieser Programmcode ausgeführt wird, bevor der Rest des Programms
                ; geladen wird. 
                ; Dies hat den Vorteil, daß die Speicherbereiche, die 
                ; standardmäßig für Anwenderprogramme gesperrt sind (BASIC und OS)
                ; freigegeben werden können und dann die entsprechenden Programmteile dorthin
                ; geladen werden
                ; $a000-$bfff (BASIC)
                ; $c000-$cfff (OS) bzw. $e000-$ffff (OS)


    run start                   

sub_rom_aus
    lda rtclok+2        ; rtclock-Warten, damit der Interrupt nicht gerade reinrauscht, beim Abschalten
    cmp rtclok+2
    beq wait

    ;VBI-Interrupt aus
    sei
    lda #0
    sta nmien

    ; ROM aus
    lda #$fe
    sta portb
    rts

sub_rom_ein
    lda #$ff
    sta portb

    ; jetzt auch den VBI-Interrupt wieder einschalten
    lda #$40
    sta nmien
    cli

    rts

sub_triggerabfrage ;joystickfeuer gedrückt?
joystickloop lda trig0
    cmp #1
    beq joystickloop

    rts

start
    ; Als erstes das ROM einschalten
    jsr sub_rom_ein

    ; Hier fehlt noch das eigentliche Programm, das über diesen Test hinausgeht
    ; ...
    ;-------------------------------------------------

    ; Vor dem Aufruf der Routine im RAM unter dem ROM, ROM ausschalten
    jsr sub_rom_aus 
    jsr sub_ramunterrom_rot
    ; Nach dem Aufruf der Routine im RAM unter dem ROM, ROM wieder einschalten
    jsr sub_rom_ein

    jsr sub_triggerabfrage

    ; Vor dem Aufruf der Routine im RAM unter dem ROM, ROM ausschalten  
    jsr sub_rom_aus
    jsr sub_ramunterrom_weiss
    ; Nach dem Aufruf der Routine im RAM unter dem ROM, ROM wieder einschalten
    jsr sub_rom_ein

endloop jmp endloop 

    org $e000

sub_ramunterrom_rot
    ; Farbe Rot
    lda #$36
    ; und damit die Rahmen-/Hintergrundfarbe setzen
    sta color4
    rts

sub_ramunterrom_weiss
    ; Farbe weiss
    lda #$0f
    ; und damit die Rahmen-/Hintergrundfarbe setzen
    sta color4
    rts 

Hallo in die Runde, ich wollte aus eigenem gegebenem Anlass das Thema RAM unter dem ROM / OS hier nochmal aufwärmen, weil ich glaube, daß dieses sehr interessante Thema noch nicht vollständig für die Allgemeinheit dargestellt wurde. Ich persönlich nutze ja das Bank Switching und bin mit meinem Hauptprogramm jetzt fast am Ende des Speichers, müsste jetzt also so langsam Hauptprogrammcode in einer andere Bank verlaagern - ca. 8kB mehr Speicher durch unnützen ROM wäre natürlich viel besser.

Nun gab es hier m Forum schon ein paar tolle und teilweise auch umfassende Lösungen, für seine Zwecke perfekt umgesetzt hat es aber wohl mein Freund Janko - zumindest genau so finde ich es persönlich optimal:

  1. Das OS wird dauerhaft abgeschaltet während das Spiel (Programm) läuft.
  2. Der interne VBI wird eigens neu geschrieben und kümmert sich um wichtige Aufgaben wie RTCLOK, bestimmte Register (DList), evt. eigene Schattenregister, Joystickabfragen, Chbas usw. Im Grunde (so habe ich es verstanden) gibt es dann auch nur noch einen einzigen VBI - der eigene neue interne und das, was man ursprünglich in den eigenen VBI coden will.
  3. Das OS wird wahlweise für bestimmte Operationen wieder angeschaltet (Tastatur, Break, Highscore speichern...).

Wie man das OS ein- und ausschaltet hat Lars in seinem Beitrag sehr schön gezeigt. Eine eigene VBI-Routine hat Janko für sich entwickelt... hier an dieser Stelle würde ich das gerne zusammenführen - so, daß es für jeden einfach einsetzbar ist.

Ich würde das gerne hier im Forum unter MADS/Eclipse entwickeln und ein komplettes lauffähiges kleines Demoprogramm daraus machen.

Das Programm soll dann folgendes können:

Ein einfacher Player kann mittels Joystick in x-Achse bewegt werden, das Hauptprogramm startet dabei bei $2000, die Playerdaten liegen in Bank1 und die Playerbase fängt im RAM unter dem ROM an. Zusätzlich haben wir eine einfache Displaylist mit einem DLI, der die Hintergrundfarbe ändert.

Ich (wir) werden das Programm hier komplett entwickeln - Schritt für Schritt. Jeder ist herzlichst aufgerufen hier rege mitzuarbeiten. Am Ende stelle ich das alles zusammen und daraus machen wir für das nächste Magazin noch einen guten Beitrag draus!

Gruß

Peter

Hallo!

Da jeder einen anderen Kenntnisstand hat, will ich einfach einmal von vorne anfangen 🙂

Das RAM unter dem OS kann man genauso wie das RAM unterm BASIC einfach über PORTB einschalten, indem man den ROM-Chip abschaltet. Es ist somit keinerlei Hexenwerk das RAM unterm OS zu nutzen...

Jedoch wird der ATARI sich aufhängen, wenn man einfach das OS abschaltet, da der ATARI einen Non Maskable Interrupt (NMI) und einen Maskable Interrupt (IRQ) besitzt und diese ohne dem OS ins Nirvana verzweigen und somit den ATARI abstürzen lassen.

Was sind das für Interrupts?

Der NMI kommt ausschließlich vom ANTIC. Der ANTIC signalisiert über NMIEN, ob es durch einen VBI oder DLI ausgelöst wurde. Der IRQ kommt vom POKEY und vom PIA. Man kann über IRQST des POKEY oder PACTL des PIA auslesen aus welchem Grund ein IRQ ausgelöst wurde. Der POKEY ist u.a. für die Tastatur zuständig. Ein Tastendruck löst somit den IRQ aus.

Den IRQ kann man einfach per CPU-Flag unterdrücken. "SEI" lässt den IRQ unterdrücken. Mir "CLI" wird dieser wieder berücksichtigt. Der NMI lässt sich nicht ausschalten... zu mindestens nicht CPU-seitig. Hier kann man aber dem ANTIC über NMIRES mitteilen, dass man keine IRQs wünscht. Sind die beide IRQs abgeschaltet, kann man den RAM unterm ROM nutzen...

Nun ja, jetzt funktioniert aber nicht mehr viel. Keine Tastatureingaben, kein VBI und kein DLI. Für Spiele ist der VBI und DLI schon sehr wichtig. Und auch die Tastatur wird immer Mal wieder benötigt. Wichtig zu wissen: Funktionstasten und Joystick benötigen keinen IRQ und funktionieren weiterhin.

Woher weiß eigentlich die CPU, was sie machen soll, wenn ein NMI oder IRQ auftritt? An den Speicherstellen $FFFA/$FFFB steht die Sprungmarke für den NMI. An $FFFE/$FFFF die Sprungmarke für den IRQ. $FFFC/$FFFD ist beim ATARI 400/800 für den Reset zuständig. Diese weisen beim eingeschaltetem OS auf die entsprechenden ROM-Routinen. Ist das OS abgeschaltet verweisen diese ins Nirvana.

Es gibt ein Assemblerlisting vom TurboBasic, wie dort mit dem Problem umgegangen wird. Es ist eigentlich sehr elegant. TB hat zwei eigene Routinen für IRQ und NMI, die im Prinzip den Stack ein wenig manipulieren, damit das ROM zu der eigenen Routine zurück kehrt. Schaltet das OS ein, springt zur gewünschten ROM-Routine, schaltet das OS wieder aus und beendet den Interrupt sozusagen.

Wenn man dauerhaft den RAM unterm OS nutzen möchte, muss man entweder die TB-Routine nutzen, oder sich etwas Eigenes basteln. Für mein Spiel benötige ich die meisten Schattenregister nicht, sondern nur ein paar (für mich) wichtige Funktionen des System-VBI. Deshalb habe ich mir aus dem OS die interessanten Funktionen in eine eigene Routine kopiert:

; --- MADS Assembler-Routine
        .PROC MyNMI
        BIT NMIST       ; Prüfen ob ein DLI ausgelöst wurde
            BPL @+
            JMP (VDSLST)        ; Wenn ja, dann ab in die DLI Routine

@           PHR

        INC RTCLOK+2        ; Systemtimer weiterlaufen lassen
        BNE @+
        INC RTCLOK+1
        BNE @+
        INC RTCLOK
@       LDA #$00
        STA SRTIMR
        MVA CHBAS  CHBASE   ; Zeichensatz Schattenregister umkopieren
        MWA SDLSTL DLISTL   ; DLI-Schattenregister umkopieren
        MWA SDMCTL DMACTL   ; DMA-Kontroll-Schattenregister umkopieren
        LDA PORTA       ; Joystick Port A ist Stick 0 & 1
        LSR         ; Diese trennen und in die passenden 
        LSR         ; Schattenregister bringen
        LSR
        LSR
        STA STICK1
        LDA PORTA
        AND #$0F
        STA STICK0
        LDA TRIG0       ; Feuerknopf ebenfalls im jeweiligen
        STA STRIG0      ; Schattenregister kopieren
        LDA TRIG1
        STA STRIG1
        LDX #$03        ; Die einzelnen Paddles in die Schattenregister
@       LDA POT0,X      ; umkopieren
        STA PADDL0,X
        DEX
        BPL @-

        PLR
        RTI
        .ENDP

Welchen Vorteil bringt mir dieses Vorgehen? Ich spare ein wenig Rechenzeit im VBI und kann damit mehr eigenen Programmcode im "nichtsichtbaren" Bereich ausführen lassen. Den IRQ brauche ich größtenteils nicht und kann diesen per SEI abschalten. Aber sollte man doch z.B. die Tastatur nutzen wollen, kann man einfach die Routine des TurboBasics nehmen (leicht abgewandelt):

; --- MADS Assembler-Routine
        .PROC MyIRQ
        PHA         ; Akku sichern
            LDA #>Exit      ; Eigene Exit-Routine in den Stack schreiben
            PHA
            LDA #<Exit
            PHA
            PHP         ; Prozessorstatus auf den Stack (für RTI)
            INC PORTB       ; OS-ROM anschalten (wichtig OS-ROM muss vorher aus sein!)
            JMP (VIMIRQ)        ; Ins Betriebssystem springen

Exit        DEC PORTB       ; OS-ROM wieder abschalten
            PLA         ; Akku holen
            RTI             ; und zurück
            .ENDP

Um die Sprungadressen an $FFFA-$FFFF sicher setzen zu können, sollte man vorher kurz die Interrupts abschalten:

; --- MADS Assembler-Routine
        SEI             ; IRQ und VBI deaktivieren
        LDA #0
        STA NMIEN

        LDA PORTB           ; OS-ROM und BASIC abschalten
        AND #%11111100
        ORA #%00000010          ; bit 0==0 OS-RAM bit 1==1 BASIC-RAM
        STA PORTB

        LDX #0              ; Neue IRQ-Sprungadressen setzen
POINTER_LOOP    LDA NEWPOINTER,X
        STA $FFFA,X
        INX
        CPX #6
        BNE POINTER_LOOP        

        LDA #NMIEN_VBI          ; VBI und IRQ wieder aktivieren
        STA NMIEN
        CLI

        ...
        ...

NEWPOINTER  .WORD MyNMI
        .WORD 0
        .WORD MyIRQ     
        .ENDP

Jetzt kann man gefahrlos das OS dauerhaft im Programm abschalten, um den RAM nutzen zu können. Je nachdem ist es ggf. sinnvoll den Zeichensatz aus dem ROM zu kopieren, sofern man keinen eigenen hat.

Grüße Janko

Meine Projekte findest Du hier...

Kveldulfur schrieb: $FFFC/$FFFD ist beim ATARI 400/800 für den Reset zuständig.

Moin!

Jetzt ist mir in der Nacht eingefallen, dass ich einen Fehler gemacht habe, kann ihn aber nicht korrigieren 😳 Die Adresse $FFFC/$FFFD ist der Reset-Vektor (auch beim XL/XE). Nur kann man diesen nicht verändern, da nach einem Reset automatisch das OS wieder beim XL/XE aktiv ist.

Grüße Janko

Meine Projekte findest Du hier...

Danke für die umfassenden Vorarbeiten zu diesem Thema (Lars und Janko) - mit der schönen eigenen VBI Routine von Janko und der IRQ Routine von Lars, kann man nun relativ einfach den RAM unter dem ROM/OS nutzen. Anbei habe ich mal versucht in einer kleinen lauffähigen Demo das so umzusetzen, daß es jeder verstehen und für sich nutzen kann. Ich habe dabei versucht möglichst die speziellen Eigenheiten von MADS nicht zu nutzen - außer MVA und MWA. MVA speicher1 speicher2. bedeutet. nur lda speicher1 sta speicher2 Bei MWA wird dann automatisch noch lda speicher1+1 sta speicher2+1 gemacht. phr und plr bedeuten: phr -> pha plr -> pla -> txa -> tay -> pha -> pla -> tya -> tax -> pha -> pla

Ansonsten sollte das Programm folgendes können:

Ein vbi ändert ständig die Hintergrundfarbe. Zwei dli teilen die Farbe des Bildschirmes. Das Hauptprogramm stellt per Random Charakter auf dem Bildschirm dar. Der Bildschirm liegt im RAM unter dem ROM (hier bei E000) - das habe ich zu Demozwecken so gewählt, in der Praxis würde ich hier was anderes reinmachen - z.B. PM-Grafik usw. Wichtig noch: Ganz am Anfang kopiere ich den Zeichensatz runter auf $1000 - der ist nämlich weg, wenn das OS aus ist. RAM unter ROM zu nutzen bringt ca. 14 kB zusätzlich nutzbaren Speicher, was erheblich ist. Man hat aber auch einiges zu beachten: Wichtige Schattenregister muss man selber in die Hardwareregister (über den eigenen VBI) schreiben. Hier kann man natürlich auch seine eigenen Schattenregister nehmen. Mehr Kontrolle - mehr Pflichten!!

Anbei der Code:

org $2000


display = $e6
charak  = $e8
charakn = $ea

vdslst = $200
nmien = $d40e
portb = $d301
porta = $d300
wsync = $d40a
colpf2 = $d018
color2 = $2c6
nmist = $d40f
rtclok = $12
srtimr = $228
chbas = $2f4
chbase = $d409
sdlstl = $230
dlistl = $d402
sdmctl = $22f
dmactl = $d400
stick0 = $278
stick1 = $279
trig0 = $d010
trig1 = $d011
strig0 = $0284
strig1 = $0285
pot0 = $d200
paddl0 = $270
colbk = $d01a
vimirq = $216
nmien_dli = $80
nmien_vbi = $40
random = $d20a
chactl = $d401
chact = $2f3

backgr      .by 00

vektoren    .word vbinter
            .word 0
            .word meinirq       


            .proc main
            ;--------------------Zeichensatz umkopieren auf $1000
            mva chbas charak+1  
            mva #$10 charakn+1
            lda #0 
            sta charak
            sta charakn
            ldy #0
            ldx #0
            clc
lp1         lda (charak),y
            sta (charakn),y
            iny
            cpy #255
            bne lp1
            ldy #0
            inc charakn+1
            inc charak+1
            inx
            cpx #4
            clc
            bne lp1


            mwa #$e000 display  ;Bildschirmstart bei $E000
            mwa #dlist sdlstl   ;Displaylist wird initialisiert
            mva #$10 chbas      ;Zeichensatz auf $1000 biegen
            mwa #dli1 vdslst    ;1.DLI Vektor setzen    

            sei                 ; IRQ und VBI deaktivieren
            lda #0
            sta nmien

            lda portb           ; OS-ROM und BASIC abschalten
            and #%11111100
            ora #%00000010      ; bit 0==0 OS-RAM bit 1==1 BASIC-RAM
            sta portb

            ldx #0              ; Neue IRQ-Sprungadressen setzen
lp2         lda vektoren,x
            sta $fffa,x
            inx
            cpx #6
            bne lp2     

            lda #nmien_vbi + nmien_dli  ; VBI und IRQ wieder aktivieren
            sta nmien
            cli
            mva chact chactl    ; Darstellung der Character

            ;--------- kurzes Hauptprogramm ---------
loop        iny
            lda random
            sta (display),y

            jmp loop            
            ;--------   Ende Hauptprogramm ----------

            .endp

            ;   ab hier stehen die Subroutinen

            .proc dli1          ; erster DLI
            phr 
            sta wsync
            mva #222 colpf2
            mwa #dli2 vdslst    ; Ende dli1 initialisiert dli2
            plr
            rti
            .endp
            .proc dli2
            phr 
            sta wsync
            mva #55 colpf2
            mwa #dli1 vdslst    ; Ende dli2 initialisiert dli1
            plr
            rti
            .endp
            .proc vbinter
            bit nmist           ; Prüfen ob ein DLI ausgelöst wurde
            bpl lp3
            jmp (vdslst)        ; Wenn ja, dann ab in die DLI Routine

lp3         phr     
            inc rtclok+2        ; Systemtimer weiterlaufen lassen
            bne lp4
            inc rtclok+1
            bne lp4
            inc rtclok
lp4         lda #$00
            sta srtimr
            mva chbas  chbase   ; Zeichensatz Schattenregister umkopieren
            mwa sdlstl dlistl   ; DLI-Schattenregister umkopieren
            mwa sdmctl dmactl   ; DMA-Kontroll-Schattenregister umkopieren
            mva color2 colpf2
            lda porta           ; Joystick Port A ist Stick 0 & 1
            lsr                 ; Diese trennen und in die passenden 
            lsr                 ; Schattenregister bringen
            lsr
            lsr
            sta stick1
            lda porta
            and #$0F
            sta stick0
            lda trig0           ; Feuerknopf ebenfalls im jeweiligen
            sta strig0          ; Schattenregister kopieren
            lda trig1
            sta strig1
            ldx #$03            ; Die einzelnen Paddles in die Schattenregister
lp5         lda pot0,x          ; umkopieren
            sta paddl0,x
            dex
            bpl lp5

            inc backgr          ; der VBI ändert die Farbe des Hintergrundbildschirmes
            mva backgr colbk    ; ein Schattenregister kann incrementiert werden
                                ; ein Hardwareregister nicht (nur schreiben)
            plr
            rti
            .endp
            .proc meinirq

            pha             ; Akku sichern
            lda #>ex        ; Eigene Exit-Routine in den Stack schreiben
            pha
            lda #<ex
            pha
            php             ; Prozessorstatus auf den Stack (für RTI)
            inc portb       ; OS-ROM anschalten (wichtig OS-ROM muss vorher aus sein!)
            jmp (vimirq)    ; Ins Betriebssystem springen

ex          dec portb       ; OS-ROM wieder abschalten
            pla             ; Akku holen
            rti             ; und zurück

            .endp

            ; hier steht die Displaylist

dlist       .by 112,112,112
            .by 66,0,224
            .by 130         ; hier wird ein DLI ausgelöst
:10         .by 2
            .by 130         ; hier wird ein DLI ausgelöst
:10         .by 2
            .by 2
            .by 65
            .wo dlist








            run main

Hallo Peter, ich habe gerade mal Deinen Code überflogen. Da ist mir im Definitionsbereich etwas aufgefallen:

srtimr = $228

Ich finde dazu eine andere Adresse im Profibuch: $22B

PS: Ich habe soeben noch etwas festgestellt.

Damit es auch wirklich immer sauber funktioniert mit dem Abschalten sollte noch vor das erste SEI folgendes kommen:

    lda:cmp:req $14     ;wait 1 frame

gleichbedeutend mit diesem loop hier:

    lda $14
lp  cmp $14
    beq lp

Wartet man nicht kann es zu (für mich zunächst) mysteriösen Abstürzen kommen. Ich hatte z.B. das Problem, dass mein Code nur mit dem ATARI OS funktionierte und das selbe Programm mit ALTIRRA OS regelmäßig den Start versagte.

PP´s of STARSOFTBerlin____github|meine Webseite|Demozoo

Vielen Dank für die Ergänzung. Da hatte ich wohl einen Tippfehler... wenn ich hier Codes reinstelle, dann versuche ich das immer so zu machen, daß man den Code einfach kopieren kann und es läuft. Dazu muss man ja die entsprechenden Hardwareadressen, die man braucht direkt in den Code einbringen. Bei mir "in Natura" sieht das ganz anders aus. Da habe ich alle Hardwareregister in einer Datei, die ich einfach per include in meinen Code einbinde....

Das mit der kleinen Warteschleife vor SEI ist interessant, das habe ich gerade in meinem Programm zusätzlich implementiert! Danke nochmals....